use sea_orm::sea_query::{self, Alias};
use sea_query::{Cond, Condition, Expr, LikeExpr, SimpleExpr};
use serde::Deserialize;

static FILTER_MATCH: once_cell::sync::Lazy<regex::Regex> =
    once_cell::sync::Lazy::new(|| regex::Regex::new(r"^(\w+)-(like|eq)-(\w+)$").unwrap());

#[derive(Debug, Clone, Deserialize)]
pub enum Filter {
    Eq(String, String),
    Like(String, String),
    NotLike(String, String),
    And(Vec<Filter>),
    Or(Vec<Filter>),
}

impl Filter {
    pub fn get_cond(&self) -> Condition {
        match &self {
            Self::Eq(name, value) => Cond::any().add(Expr::col(Alias::new(name)).eq(value)),
            Self::Like(name, value) => {
                Cond::any().add(Expr::col(Alias::new(name)).like(LikeExpr::new(value)))
            }
            Self::NotLike(name, value) => {
                Cond::any().add(Expr::col(Alias::new(name)).not_like(LikeExpr::new(value)))
            }
            Self::And(filters) => filters
                .iter()
                .fold(Cond::all(), |expr, x| expr.add(x.get_cond())),
            Self::Or(filters) => filters
                .iter()
                .fold(Cond::any(), |expr, x| expr.add(x.get_cond())),
        }
    }

    #[deprecated]
    pub fn get_expr(&self) -> SimpleExpr {
        match &self {
            Self::Eq(name, value) => Expr::col(Alias::new(name)).eq(value),
            Self::Like(name, value) => Expr::col(Alias::new(name)).like(LikeExpr::new(value)),
            Self::NotLike(name, value) => {
                Expr::col(Alias::new(name)).not_like(LikeExpr::new(value))
            }
            Self::And(filters) => filters
                .iter()
                .fold(Expr::value(1), |expr, x| expr.and(x.get_expr())),
            Self::Or(filters) => filters
                .iter()
                .fold(Expr::value(0), |expr, x| expr.or(x.get_expr())),
        }
    }
}

impl Into<Vec<Filter>> for Filter {
    fn into(self) -> Vec<Filter> {
        vec![self]
    }
}

impl Into<Filter> for (String, String) {
    fn into(self) -> Filter {
        Filter::Eq(self.0, self.1)
    }
}

impl TryFrom<&str> for Filter {
    type Error = String;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let caps = FILTER_MATCH.captures(value);
        if let Some(caps) = caps {
            let opt = caps.get(2).unwrap().as_str();
            Ok(match opt {
                "like" => Self::Like(
                    caps.get(1).unwrap().as_str().to_owned(),
                    caps.get(3).unwrap().as_str().to_owned(),
                ),
                "eq" => Self::Eq(
                    caps.get(1).unwrap().as_str().to_owned(),
                    caps.get(3).unwrap().as_str().to_owned(),
                ),
                _ => Err("()".to_string())?,
            })
        } else {
            Err("()".to_string())
        }
    }
}
